﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GameLib.WorldMap;
using System.Xml;
using GameLib.Serialization;
using Sandbox.Serialization.StringSerializers;
using GameLib.Main;
using GameLib.Utils;

namespace Sandbox.Serialization.XmlSerializers
{
	public class MapSerializer : XmlSerializer<Map>
	{
		public MapSerializer()
		{
			RegisterPropertyHandler("Tiles", DeserializeTiles, SerializeTiles);
		}

		protected override object DeserializeInnerTyped(Map obj, Type type, XmlReader reader, DataFlags dataFlags)
		{
			if (Locked)
				throw new Exception("There could only be one MapSerializer working!");
			Locked = true;
			TilesLoaded = false;
			PreCreatedTiles.Clear();
			Map = obj;

			TileLocSerializer tileSerializer = DataHandler.GetSerializer(typeof(Tile), GameDataTypes.Text) as TileLocSerializer;
			tileSerializer.CurrentMapSerializer = this;

			object ret = base.DeserializeInnerTyped(obj, type, reader, dataFlags);
			
			tileSerializer.CurrentMapSerializer = null;
			Locked = false;
			TilesLoaded = false;
			Map = null;
			PreCreatedTiles.Clear();
			return ret;
		}

		public Tile GetTile(int x, int y)
		{
			if (TilesLoaded)
			{
				return Map.GetTile(x, y);
			}
			else
			{
				Tile tile = new Tile(Map, x, y);
				PreCreatedTiles.Add(tile);
				return tile;
			}
		}

		private bool Locked
		{
			get;
			set;
		}

		private bool TilesLoaded
		{
			get;
			set;
		}

		private Map Map
		{
			get;
			set;
		}

		private object DeserializeTiles(object obj, Type type, string propName, XmlReader reader, DataFlags dataFlags)
		{
			MapTileCollection tiles = obj as MapTileCollection;

			if (tiles == null || tiles.Map != Map)
				throw new Exception("The tile collection we are loading is inconsistent with what current map has");

			if (reader.IsEmptyElement)
			{
				LoadTiles(tiles, "");
				reader.Read();
				return obj;
			}

			reader.ReadStartElement();
			reader.MoveToContent();

			if (reader.NodeType != XmlNodeType.Text)
				throw new Exception(string.Format("MapTilesSerializer can only read string element, current: {0}={1}, type:{2}", reader.Name, reader.Value, reader.NodeType));

			LoadTiles(tiles, reader.Value);

			reader.Read();
			reader.MoveToContent();

			reader.ReadEndElement();

			return obj;
		}

		private void LoadTiles(MapTileCollection tiles, string content)
		{
			string[] rows = content.Split(',');

			if (rows.Length == 0)
			{
				Map.Init(0, 0);

				if (PreCreatedTiles.Count > 0)
					throw new Exception("Map inited as empty but pre crated tiles is not!");
			}
			else
			{
				int w = rows[0].Trim('\n').Length;
				int h = rows.Length;

				Map.Init(w, h);

				foreach (Tile tile in PreCreatedTiles)
				{
					if (Map.GetTile(tile.X, tile.Y) != null)
					{
						GameVector coord = Map.ToAlignedCoordinate(tile.X, tile.Y);
						tiles[coord.X, coord.Y] = tile;
					}
					else
					{
						throw new Exception("Failed to reattach the pre created tile to real tile list:" + tile.ToString());
					}
				}

				for (int y = 0; y < h; y++)
				{
					string row = rows[y].Trim('\n');
					if (row.Length != w)
						throw new Exception(string.Format("Tile def error: each row must be aligned, expect length {0}, current: {1} {2}", w, row.Length, row));

					for (int x = 0; x < w; x++)
					{
						char c = row[x];
						if (!char.IsWhiteSpace(c) && tiles[x, y] != null)
						{
							/*
							Tile tile = new Tile(obj.Map, x, y);
							tile.Terrain = Core.Play.World.Terrains.Find(c);
							obj[x, y] = tile;*/
							tiles[x, y].Terrain = Core.Play.World.Terrains.Find(c);
						}
					}
				}
			}

			PreCreatedTiles.Clear();
			TilesLoaded = true;
		}

		private void SerializeTiles(object obj, Type type, string propName, XmlWriter writer, DataFlags dataFlags)
		{
			MapTileCollection tiles = obj as MapTileCollection;

			if (tiles == null)
				throw new Exception("The tile collection we are loading is inconsistent with what current map has");

			writer.WriteStartElement(propName);

			StringBuilder sb = new StringBuilder(tiles.Height * (tiles.Width + 1));

			sb.Append("\n");

			for (int y = 0; y < tiles.Height; y++)
			{
				for (int x = 0; x < tiles.Width; x++)
				{
					Tile tile = tiles[x, y];
					sb.Append(tile != null ? tile.Terrain.Id : ' ');
				}

				if (y < tiles.Height - 1)
					sb.Append(",\n");
			}
			writer.WriteString(sb.ToString());

			writer.WriteEndElement();
		}

		private List<Tile> PreCreatedTiles = new List<Tile>();
	}
}
